package com.googlecode.axolotl.jpa.metaclass;

import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;

import java.util.Map;
import java.util.regex.Pattern;

import javax.persistence.EntityManagerFactory;

import org.codehaus.groovy.grails.commons.GrailsApplication;
import org.codehaus.groovy.grails.commons.GrailsClassUtils;
import org.codehaus.groovy.grails.commons.GrailsDomainClass;
import org.codehaus.groovy.grails.validation.CascadingValidator;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import com.googlecode.axolotl.grails.GrailsDomainUtils;


public class SavePersistentMethod extends AbstractDynamicPersistentMethod {

    public static final String METHOD_SIGNATURE = "_save";
    public static final Pattern METHOD_PATTERN = Pattern.compile('^' + METHOD_SIGNATURE + '$');

    private static final String ARGUMENT_VALIDATE = "validate";
    private static final String ARGUMENT_DEEP_VALIDATE = "deepValidate";
    private static final String ARGUMENT_FLUSH = "flush";

    
	public SavePersistentMethod(EntityManagerFactory emf, GrailsApplication application) {
		super(emf, application, METHOD_PATTERN);
	}

	
	@Override
	protected Object doInvokeInternal(Object target, Object[] arguments) {
		GrailsDomainClass domainClass = GrailsDomainUtils.getDomainClass(getGrailsApplication(), 
				target.getClass());
		
		if (shouldValidate(arguments, domainClass)) {
			Validator validator = domainClass.getValidator();
			MetaClass mc = GroovySystem.getMetaClassRegistry().getMetaClass(target.getClass());

			Errors errors = new BeanPropertyBindingResult(target, target.getClass().getName());
			mc.setProperty(target, "errors", errors);

			if (validator != null) {
				boolean deepValidate = true;
				if (arguments.length > 0) {
					if (arguments[0] instanceof Map) {
						Map argsMap = (Map) arguments[0];
						if (argsMap.containsKey(ARGUMENT_DEEP_VALIDATE)) {
							deepValidate = GrailsClassUtils.getBooleanFromMap(ARGUMENT_DEEP_VALIDATE, argsMap);
						}
					}
				}
				
				if (deepValidate && (validator instanceof CascadingValidator)) {
					((CascadingValidator) validator).validate(target, errors, deepValidate);
				}
				else {
					validator.validate(target,errors);
				}

				if (errors.hasErrors()) {
//					return handleValidationError(target,errors);
				}
			}
		}
		
		return performSave(target, shouldFlush(arguments));
	}

	
	protected Object performSave(final Object target, final boolean flush) {
		getJpaTemplate().persist(target);
		return target;
	}
	

    private boolean shouldFlush(Object[] arguments) {
        if (arguments.length > 0) {
            if (arguments[0] instanceof Boolean) {
                return (Boolean) arguments[0];
            }
            else if (arguments[0] instanceof Map) {
                Map argsMap = (Map) arguments[0];
                if (argsMap.containsKey(ARGUMENT_FLUSH)) {
                    return GrailsClassUtils.getBooleanFromMap(ARGUMENT_FLUSH, argsMap);
                }
            }
        }
        return false;
	}

    
	private boolean shouldValidate(Object[] arguments, GrailsDomainClass domainClass) {
		if (domainClass != null) {
            if (arguments.length > 0) {
                if (arguments[0] instanceof Boolean) {
                    return (Boolean) arguments[0];
                }
                else if (arguments[0] instanceof Map) {
                	Map argsMap = (Map) arguments[0];
                    if (argsMap.containsKey(ARGUMENT_VALIDATE)) {
                        return GrailsClassUtils.getBooleanFromMap(ARGUMENT_VALIDATE, argsMap);
                    }
                }
            }
           	return true;
        }
		return false;
	}

}
